#ifndef MINI_FUNCS_H
#define MINI_FUNCS_H

#pragma once

#include "stdafx.h"
#include "Variable.h"
#include <boost/variant.hpp>
#include <boost/type_traits/is_same.hpp>
#include "Operators.h"

namespace MiniBasic
{
    template<class Type>
    struct apply_result
    {
        Type result;
        DWORD wrongArgFlags;
        apply_result(const Type& result, DWORD wrongArgFlags = 0) : result(result), wrongArgFlags(wrongArgFlags) {}
        operator bool() const
        {
            return !wrongArgFlags;
        }
    };

    template<class Type>
    struct get_underlying_value : public boost::static_visitor<apply_result<Type> >
    {
        typedef typename boost::static_visitor<apply_result<Type> >::result_type result_type;
    
        result_type operator()(const Type& variable) const
        {
            return result_type(variable);
        }

        result_type operator()(Variable* var) const
        {
            return boost::apply_visitor(*this, var->value);
        }

        template<class U>
        result_type operator()(const U&) const
        {
            return result_type(Type(), 1);
        }
    };

    template<>
    struct get_underlying_value<Variable*> : public boost::static_visitor<apply_result<Variable*> >
    {
        typedef boost::static_visitor<apply_result<Variable*> >::result_type result_type;
        result_type operator()(Variable* var) const
        {
            return result_type(var);
        }

        template<class U>
        result_type operator()(const U&) const
        {
            return result_type(static_cast<Variable*>(0), 1);
        }
    };

    template<
        template<class T> class Pred,
        class Type = mbint,
        class SpecializedPred = Pred<mbint> 
        >
    struct apply_unary : public boost::static_visitor<apply_result<Type> >
    {
    private:
        SpecializedPred pred;

    public:
        typedef typename boost::static_visitor<apply_result<Type> >::result_type result_type;

        apply_unary(const SpecializedPred& pred = SpecializedPred()) : pred(pred) {}
        
        result_type operator()(Type& variable) const
        {
            return result_type(pred(variable));
        }

        result_type operator()(Variable* var) const
        {
            return boost::apply_visitor(*this, var->value);
        }

        template<class U>
        result_type operator()(const U&) const
        {
            return result_type(Type(), 1);
        }
    };

    template<
        template<class T> class Pred,
        class Type = mbint,
        class SpecializedPred = Pred<Type> 
        >
    struct apply_binary : public boost::static_visitor<apply_result<Type> >
    {
    private:
        SpecializedPred pred;

    public:
        typedef typename boost::static_visitor<apply_result<Type> >::result_type result_type;

        apply_binary(const SpecializedPred& pred = SpecializedPred()) : pred(pred) {}

        result_type operator()(Variable* var1, Variable* var2) const
        {
            return boost::apply_visitor(*this, var1->value, var2->value);
        }

        result_type operator()(const Type& var1, Variable* var2) const
        {
            boost::variant<Type> temp(var1);
            return boost::apply_visitor(*this, temp, var2->value);
        }

        result_type operator()(Variable* var1, const Type& var2) const
        {
            boost::variant<Type> temp(var2);
            return boost::apply_visitor(*this, var1->value, temp);
        }

        result_type operator()(Type& var1, Type& var2) const
        {
            return result_type(pred(var1, var2));
        }

        template<class T1, class T2>
        result_type operator()(const T1&, const T2&) const
        {
            DWORD wrongTypes = 0;
            if(!boost::is_same<T1, Type>::value)
            {
                wrongTypes += 1;
            }
            if(!boost::is_same<T2, Type>::value)
            {
                wrongTypes += 2;
            }
            return result_type(Type(), wrongTypes);
        }
    };
}

#endif
